import { Injectable, Inject, NgZone } from '@angular/core';
import { ActivatedRouteSnapshot, Router } from '@angular/router';

import { pick } from '../../util';
import { APP_BASE_HREF, PlatformLocation } from '@angular/common';
import { LoggerService } from './logger.service';
import { NAV_TAB_MAX_CACHE } from '../keys';
import { BroadcastService } from './broadcast.service';

/**
 * 路由复用原则：
 * 1. 系统中每个叶子路由的层级深度必须一直
 * 2. 路由跳转时，只保存上一个叶子路由的状态信息（根路由除外）
 * 3. 同模块路由跳转时父级路由状态信息会保持共享
 * 4. 不同模块路由跳转时父级路由状态信息会销毁
 */
@Injectable({
  providedIn: 'root'
})
export class NavTabService<B> {
  // 强制刷新叶子路由节点
  public focusRefreshLeafRoute = false;
  routeCache = [];
  willRemoveUrl;
  private baseUrl = '/';
  private _execHooks: { handle: Function, type: string }[] = [];
  private spaMenuList = [];
  constructor(
    @Inject(NAV_TAB_MAX_CACHE) private maxCache: number,
    private location: PlatformLocation,
    private broadcastService: BroadcastService<B>,
    private ngZone: NgZone,
    private loggerService: LoggerService
  ) {
    this.baseUrl = this.location.getBaseHrefFromDOM();
  }

  // 决定是否要将上一个路由相关的数据备份起来，排除异步节点，父节点
  shouldDetach(route: ActivatedRouteSnapshot): boolean {
    // if (!route.routeConfig || route.routeConfig.loadChildren) {
    //   return false
    // }

    if (this.focusRefreshLeafRoute || route.data.disableReuse || !route.component || (route.children && route.children.length > 0)) {
      return false;
    }

    const url = this.parseUrl(route);
    if (this.willRemoveUrl && url === this.willRemoveUrl && (!route.children || route.children.length === 0)) {
      this.willRemoveUrl = null;
      return false;
    }

    return true;
  }

  /** 缓存路由 */
  store(route: ActivatedRouteSnapshot, handle: any): void {
    const index = this.getRouteIndex(route);

    // 只清除叶子结点路由
    if (!handle) {
      // 只删除叶子结点
      if (!route.children || route.children.length <= 0) {
        if (index >= 0) {
          this.routeCache.splice(index, 1);
        }
      }

      return;
    }

    const _cache = {
      url: this.parseUrl(route),
      params: JSON.stringify(route.params),
      queryParams: JSON.stringify(pick(route.queryParams, '** -target')),
      handle: handle,
      time: Date.now(),
      pathCountFromRoot: route.pathFromRoot ? route.pathFromRoot.length : 0,
      component: route.component
    };

    if (index >= 0) {
      this.routeCache[index] = _cache;
    } else {
      this.addCache(_cache);
    }

    this.execHook(handle, 'reuseOnDestroy');

  }

  // 决定是否要从缓存中检索跟当前路由相关的备份数据
  shouldAttach(route: ActivatedRouteSnapshot): boolean {
    if (this.focusRefreshLeafRoute || route.data.disableReuse) {
      return false;
    }

    const routeIndex = this.getRouteIndex(route);
    return routeIndex !== -1;
  }

  /** 从Cache 中取得对应的暂存内容 */
  retrieve(route: ActivatedRouteSnapshot): {} {
    // if (!this.isValidRoute(route)) {
    //   return null;
    // }

    const routeIndex = this.getRouteIndex(route);
    const handle = routeIndex !== -1 ? this.routeCache[routeIndex].handle : null;
    this.execHook(handle, 'reuseOnInit');
    return handle;
  }

  /**
   * 路由复用的前提条件是两个路由层级深度一致！！！！
   */
  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
    if (future.parent === null && curr.parent === null) {
      if (future.routeConfig !== curr.routeConfig) {
        this.loggerService.warn('reuse-route: rootFutureRoute.routeConfig !== rootCurrRoute.routeConfig');
      }
      // 根路由一律复用
      return true;
    } else if ((future.component && future.component === curr.component) || (future.routeConfig && future.routeConfig === curr.routeConfig)) {
      // component 非空且相等(普通路由) 或者 routeConfig 非空且相等(异步路由)
      if (future.children && future.children.length > 0 && curr.children && curr.children.length > 0) {
        // 如果是父节点路由无条件复用
        return true;
      } else if ((!future.children || future.children.length <= 0) && (!curr.children || curr.children.length <= 0)) {
        // 如果是叶子节点需要判断queryParams和params
        if (this.focusRefreshLeafRoute) {
          // 如果强制刷新则不复用
          return false;
        } else {
          // 如果是叶子节点路由需要判断url是否完全不相等
          return JSON.stringify(future.queryParams) === JSON.stringify(curr.queryParams)
            && JSON.stringify(future.params) === JSON.stringify(curr.params);
        }
      } else {
        // 路由层级不一致的情况。。。。。。。。。。。。。。
        return false;
      }
    }

    return false;
  }

  public remove(urls: string[]) {
    let removedIndex = 0;
    for (let i = 0; i < this.routeCache.length; i++) {
      const cache = this.routeCache[i];
      removedIndex = urls.indexOf(cache.url);
      if (removedIndex !== -1) {
        const handle = this.routeCache[i].handle;
        this.execHook(handle, 'ngOnDestroy');
        this.routeCache.splice(i, 1);
        i--;
        urls.splice(removedIndex, 1);
        removedIndex = -1;
      }

      if (urls.length === 0) {
        return;
      }
    }
    if (urls.length > 0) {
      this.willRemoveUrl = urls[0];
    }
  }

  public willRemove(url) {
    this.willRemoveUrl = url;
    for (let i = 0; i < this.routeCache.length; i++) {
      const cache = this.routeCache[i];
      if (cache.url === url) {
        this.routeCache.splice(i, 1);
        return;
      }
    }
  }

  public parseUrl(route: ActivatedRouteSnapshot) {
    let endRoute = route;
    const urlArr = [];
    while (endRoute.firstChild) {
      endRoute = endRoute.firstChild;
    }
    while (endRoute) {
      urlArr.push(endRoute.url.join('/'));
      endRoute = endRoute.parent;
    }
    return '/' + urlArr.filter(url => !!url).reverse().join('/');
  }

  private addCache(cache) {
    if (this.routeCache.length < this.maxCache) {
      this.routeCache.push(cache);
      return;
    }

    const removeIndex = this.routeCache.reduce((minIndex, _cache, index) => {
      if (_cache.time < this.routeCache[minIndex].time) {
        return index;
      }

      return minIndex;
    }, 0);

    this.routeCache.splice(removeIndex, 1);
    this.routeCache.push(cache);
  }

  private execHook(handle: any, type: string) {
    const hook = this._execHooks.find((item) => {
      return item.handle === handle && type === item.type;
    });

    // 修复angular bug，避免hook重复多次
    if (hook) {
      return;
    }

    this.ngZone.runOutsideAngular(() => {
      this._execHooks.push({ handle, type });

      Promise.resolve().then(() => {
        if (type === 'reuseOnInit') {
          this.broadcastService.emit(<any>'ReuseOnInit');
        } else if (type === 'reuseOnDestroy') {
          this.broadcastService.emit(<any>'ReuseOnDestroy');
        }

        if (handle && handle.componentRef && handle.componentRef.instance && handle.componentRef.instance[type]) {
          const fn = handle.componentRef.instance[type];
          fn.apply(handle.componentRef.instance);
        }

        const index = this._execHooks.findIndex((item) => {
          return item.handle === handle && type === item.type;
        });
        this._execHooks.splice(index, 1);
      });
    });
  }

  private getRouteIndex(route: ActivatedRouteSnapshot) {
    const url = this.parseUrl(route);
    const queryParams = JSON.stringify(pick(route.queryParams, '** -target -_fixed -_closeable'));
    const pathCountFromRoot = route.pathFromRoot ? route.pathFromRoot.length : 0;
    return this.routeCache.findIndex((item: any) => {
      const _queryParams = JSON.stringify(pick(JSON.parse(item.queryParams), '** -target -_fixed -_closeable'));
      return item.pathCountFromRoot === pathCountFromRoot && item.component === route.component && item.url === url && _queryParams === queryParams;
    });
  }

}

